#include "GRO_Gui.h"
#include "GRO_Group.h"
#include "GRO_Panel.h"
#include "GRO_Text.h"
#include "GRO_Checkbox.h"

using namespace std;

namespace GRO
{

// Globals
unsigned int Widget::lastID = 0;

FontMap* globalFontMap = new FontMap;


FontMap* getFontMap()
{
    return globalFontMap;
}

void setFontMap(FontMap* fontMap)
{
    globalFontMap = fontMap;
}


// Every child of Widget should use these in draw()
#define DRAW_BOX     painters.box.draw(area, _gui->getRenderer());
#define DRAW_FRAME   painters.frame.draw(area, _gui->getRenderer());
#define DRAW_DESIGN  painters.design.drawDesign(area, designPush, _gui->getRenderer());

FontMap::FontMap()
{}

FontMap::~FontMap()
{
    for(std::map<GRO_FontID, GRO_Font*>::iterator e = fonts.begin(); e != fonts.end(); e++)
    {
        delete e->second;
    }
    
}

void FontMap::setIndexedFont(unsigned int i, const GRO_FontID& id)
{
    while(i >= indexedFonts.size())
    {
        indexedFonts.push_back(GRO_FontID());
    }
    
    indexedFonts[i] = id;
}

GRO_Font* FontMap::getIndexedFont(unsigned int i)
{
    return get(indexedFonts[i]);
}

GRO_FontID FontMap::add(GRO_Font* font)
{
    if(font == NULL)
        return GRO_FontID(0);
    
    GRO_FontID id(fonts.size()+1);
    fonts.insert(std::make_pair(id, font));
    
    if(fonts.size() == 1)
        defaultFont = id;
    
    return id;
}

GRO_Font* FontMap::get(const GRO_FontID& id)
{
    if(id.getValue() == 0)
        return NULL;
    return fonts[id];
}

GRO_Font* FontMap::remove(const GRO_FontID& id)
{
    if(id.getValue() == 0)
        return NULL;
    GRO_Font* font = fonts[id];
    fonts.erase(id);
    
    if(id == defaultFont)
    {
        if(fonts.size() > 0)
            defaultFont = (fonts.begin())->first;
        else
            defaultFont = GRO_FontID(0);
    }
    
    return font;
}

GRO_Font* FontMap::getDefault()
{
    return get(defaultFont);
}







void WidgetMap::add(Widget* w)
{
    if(w == NULL)
        return;
        
    widgets.insert(w);
    if(w->name.size() > 0)
        names.insert(make_pair(w->name, w));
    ids.insert(make_pair(w->id, w));
}

void WidgetMap::remove(Widget* w)
{
    if(w == NULL)
        return;
    
    widgets.erase(w);
    names.erase(w->name);
    ids.erase(w->id);
}

void WidgetMap::remove(const std::string& name)
{
    Widget* w = get(name);
    if(w == NULL)
        return;
    
    widgets.erase(w);
    names.erase(w->name);
    ids.erase(w->id);
}

void WidgetMap::remove(const GRO_WidgetID& id)
{
    Widget* w = get(id);
    if(w == NULL)
        return;
    
    widgets.erase(w);
    names.erase(w->name);
    ids.erase(w->id);
}


unsigned int WidgetMap::size() const
{
    return widgets.size();
}

Widget* WidgetMap::get(const std::string& name)
{
    return names[name];
}

Widget* WidgetMap::get(const GRO_WidgetID& id)
{
    return ids[id];
}










Widget::Widget(TypeEnum Type, const Dimensions& dims)
    : _type(Type)
    , _gui(NULL)
    , _panel(NULL)
    , _group(NULL)
    , _parent(NULL)
    , _depth(128)
    , _hidden(false)
    , _folded(false)
    , id(lastID++)
    , grabScrollFocus(false)
    , grabKeyboardFocus(false)
    //, pinned(false)
    //, relPinCoords(NULL)
    //, relPinCoordsOffset(NULL)
    //, scrollable(1)
    , dims(dims)
    , designPush(NULL)
{
    
}


void Widget::setDepth(Uint8 Depth)
{
    _depth = Depth;
//    if (_group != NULL && pinned)
//        _group->sortPinned();
}


void Widget::draw(SDL_Rect* clipRect)
{
    if (_folded || _hidden || _gui == NULL || _gui->getRenderer() == NULL)
        return;

    SDL_Rect area = MakeRect(pos.x, pos.y, dims.w, dims.h);
    DRAW_BOX;
    DRAW_FRAME;
    DRAW_DESIGN;
}


void Widget::move(int x, int y, bool relative)
{
    if (relative)
    {
        pos.x += x;
        pos.y += y;
    }
    else
    {
        pos.x = x;
        pos.y = y;
    }
}


Widget* Widget::action(PointerCursor* mouse, ActionEnum actionType)
{
    if (_folded || _hidden || mouse == NULL)
        return NULL;
    if (InRect(mouse->mx, mouse->my, pos, dims))
        return this;
    return NULL;
}
Widget* Widget::action(NavCursor* nav, ActionEnum actionType)
{
    if (_folded || _hidden || nav == NULL)
        return NULL;
    return this;
}

Widget* Widget::findWidget(int mx, int my)
{
    if (_folded || _hidden)
        return NULL;
    if (InRect(mx, my, pos, dims))
        return this;
    return NULL;
}


void Widget::setGui(Gui* Gui)
{
    if(_gui != NULL)
        _gui->unregisterWidget(this);
    if(Gui != NULL)
        Gui->registerWidget(this);
    _gui = Gui;
}


bool getBool(const std::string& s)
{
    if(s == "0" || s == "false")
        return false;
    
    return true;
}

bool getInt(const std::string& s)
{
    return atoi(s.c_str());
}

bool getFloat(const std::string& s)
{
    return atof(s.c_str());
}

Beacon* getBeaconByName(Widget* w, const std::string& name)
{
    if(name == "onPress")
        return &w->onPress;
    else if(name == "onRelease")
        return &w->onRelease;
    else if(name == "onClick")
        return &w->onClick;
    else if(name == "onDoubleClick")
        return &w->onDoubleClick;
    else if(name == "onScrollUp")
        return &w->onScrollUp;
    else if(name == "onScrollDown")
        return &w->onScrollDown;
    else if(name == "onMotion")
        return &w->onMotion;
    else if(name == "onDrag")
        return &w->onDrag;
    else if(name == "onHover")
        return &w->onHover;
    else if(name == "onEnter")
        return &w->onEnter;
    else if(name == "onLeave")
        return &w->onLeave;
        
    return NULL;
}

Beacon* getBeaconByName(Widget* w, const std::string& name);

void Widget::connectByName(const std::string& widget, const std::string& beacon, const std::string& fn_name, const std::vector<std::string>& args)
{
    if(_gui == NULL)
        return;
        
    Widget* w = _gui->widgets.get(widget);
    if(w == NULL)
        return;
    
    Beacon* b = getBeaconByName(w, beacon);
    if(b == NULL)
        return;
    
    connectBeacon(w, b, fn_name, args);
    return;
}

void Widget::connectByWidget(Widget* widget, const std::string& beacon, const std::string& fn_name, const std::vector<std::string>& args)
{
    Widget* w = widget;
    if(w == NULL)
        return;
    
    Beacon* b = getBeaconByName(w, beacon);
    if(b == NULL)
        return;
    
    connectBeacon(w, b, fn_name, args);
    return;
}

void Widget::connectBeacon(Widget* widget, Beacon* beacon, const std::string& fn_name, const std::vector<std::string>& args)
{
    Widget* w = widget;
    if(w == NULL)
        return;
    
    Beacon* b = beacon;
    if(b == NULL)
        return;
    
    if(fn_name == "fold" && args.size() == 1)
    {
        b->connect<Widget, void, bool>(this, &Widget::fold, getBool(args[0]));
    }
    else if(fn_name == "fold" && args.size() == 0)
    {
        b->connect<Widget, void>(this, &Widget::fold);
    }
    else if(fn_name == "unfold" && args.size() == 0)
    {
        b->connect<Widget, void>(this, &Widget::unfold);
    }
    else if(fn_name == "toggleFolded" && args.size() == 0)
    {
        b->connect<Widget, void>(this, &Widget::toggleFolded);
    }
    else if(fn_name == "hide" && args.size() == 1)
    {
        b->connect<Widget, void, bool>(this, &Widget::hide, getBool(args[0]));
    }
    else if(fn_name == "hide" && args.size() == 0)
    {
        b->connect<Widget, void>(this, &Widget::hide);
    }
    else if(fn_name == "unhide" && args.size() == 0)
    {
        b->connect<Widget, void>(this, &Widget::unhide);
    }
    else if(fn_name == "toggleHidden" && args.size() == 0)
    {
        b->connect<Widget, void>(this, &Widget::toggleHidden);
    }
    return;
}










void Gui::init(SDL_PixelFormat* format, unsigned int width, unsigned int height)
{
    PointerCursor* mc = new PointerCursor(new Mouse());
    mc->gui = this;
    cursors.push_back(mc);
    
    dims.w = width;
    dims.h = height;
    /*area.x = 0;
    area.y = 0;
    area.w = width;
    area.h = height;*/
}




















Gui::Gui()
    : cursorFocus(NULL)
    , dt(0)
    , time(0)
//    , currentEvent(NULL)
{
    PointerCursor* mc = new PointerCursor(new Mouse());  // auto-delete?
    mc->gui = this;
    cursors.push_back(mc);
}

Gui::~Gui()
{
    delete renderer;
    
    
    for (list<Panel*>::iterator e = panels.begin(); e != panels.end(); e++)
    {
        delete *e;
    }
    
    
    for (list<Shortcut*>::iterator e = shortcuts.begin(); e != shortcuts.end(); e++)
    {
        delete *e;
    }
    
    for (list<Cursor*>::iterator e = cursors.begin(); e != cursors.end(); e++)
    {
        delete *e;
    }
}


Renderer* Gui::getRenderer() const
{
    return renderer;
}


void Gui::setRenderer(Renderer* render)
{
    renderer = render;
}

void Gui::freeRenderer()
{
    delete renderer;
    renderer = NULL;
}


Panel* Gui::add(Panel* panel)
{
    if (panel != NULL)
    {
        panel->setGui(this);
        //if (autoApplyTheme && theme != NULL)
            //panel->applyTheme(theme, 1);
        panels.push_back(panel);
    }
    return panel;
}


void Gui::registerWidget(Widget* widget)
{
    if(widget == NULL)
        return;
    
    if(widget->getGui() != NULL)
        widget->getGui()->unregisterWidget(widget);
    widgets.add(widget);
}

void Gui::unregisterWidget(Widget* widget)
{
    if(widget == NULL)
        return;
    
    widgets.remove(widget);
}


Widget* Gui::event(SDL_Event& Event)
{
    //currentEvent = &Event;

    Widget* result = NULL;

    // This should switch to using a default Keyboard object that was set (like cursorFocus) in
    // a Cursor object.  I guess the gui must hold the default keyboard.
    if (Event.type == SDL_KEYDOWN)
    {
        if (cursorFocus != NULL)
        {
            result = cursorFocus;
            cursorFocus->keyboardEvent(Event);  // Now, the widget can set gui::cursorFocus to NULL.
        }
        // Shortcuts
        if(result == NULL)
        {
            for (list<Shortcut*>::iterator e = shortcuts.begin(); e != shortcuts.end(); e++)
            {
                if (*e != NULL)
                {
                    if((*e)->event(Event.key.keysym))
                        return NULL;  // FIXME:  This isn't quite right.  I should probably pass along an EventResult.
                }
            }
        }
    }
    else if (Event.type == SDL_KEYUP)
    {
        if (cursorFocus != NULL)
        {
            result = cursorFocus;
            cursorFocus->keyboardEvent(Event);  // Now, the widget can set gui::cursorFocus to NULL.
        }
    }

    // New input handling

    for (list<Cursor*>::iterator e = cursors.begin(); e != cursors.end(); e++)
    {
        if (*e != NULL)
        {
            // FIXME: Needs test for result being NULL and cursor saying to still accept events
            // i.e. The event was already caught by something, should I still tell the cursor?
            EventResult r = (*e)->event(Event);
            if (r.caught)
                return r.widget;
        }
    }
    

    return result;
}



Widget* Gui::findWidget(int mx, int my, bool allowGrabbing, bool setPanelFocus)
{
    Widget* result = NULL;
    Panel* active = NULL;
    // search from the top
    for (list<Panel*>::reverse_iterator e = panels.rbegin(); e != panels.rend(); e++)
    {
        result = (*e)->findWidget(mx, my);
        if (setPanelFocus && InRect(mx, my, (*e)->pos, (*e)->dims))
            active = *e;
         
        // If we found a widget from a top panel, we're done.
        if (result != NULL)
            break;
        
        // Don't process any other panels if this one is greedy,
        // but don't let hidden stuff be greedy.
        if (allowGrabbing && (*e)->grabInput && !(*e)->isFolded() && !(*e)->isHidden())
            break;
    }
    // Put active panel on top
    if (setPanelFocus && active != NULL && !active->keepDepth)
    {
        panels.remove(active);
        panels.push_back(active);
    }

    return result;
}





    
void Gui::addShortcut(Shortcut* shortcut)
{
    if(shortcut != NULL)
        shortcuts.push_back(shortcut);
}




void Gui::draw()
{
    Renderer* currentRenderer = getRenderer();
    if (currentRenderer == NULL)
        return;
    
    currentRenderer->ReadyDraw();
    SDL_Rect clip = currentRenderer->GetClip();  // Respect the existing clip rect

    for (list<Panel*>::iterator e = panels.begin(); e != panels.end(); e++)
    {
        (*e)->draw(&clip);
    }
    currentRenderer->SetClip(clip);
    for (list<Cursor*>::iterator e = cursors.begin(); e != cursors.end(); e++)
    {
        (*e)->draw();
    }
    currentRenderer->SetClip(clip);
    currentRenderer->DoneDraw();  // Should cursors be allowed to clip?
}

void Gui::update()
{
    Uint32 temp = time;
    time = SDL_GetTicks();
    dt = (time - temp)/1000.0f;

    for (list<Cursor*>::iterator e = cursors.begin(); e != cursors.end(); e++)
    {
        (*e)->update();
    }

    for (list<Panel*>::iterator e = panels.begin(); e != panels.end(); e++)
    {
        (*e)->update();
    }

}


void Gui::registerBeacon(Widget* widget, const std::string& beaconName, Widget* fn_widget, const std::string& fn_name)
{
    if(widget == NULL || fn_widget == NULL)
        return;
        
    RegisteredBeacon rb("", beaconName, "", fn_name);
    rb.widgetPtr = widget;
    rb.fn_widgetPtr = fn_widget;
    
    registeredBeacons.push_back(rb);
    
    Widget* fn_w = fn_widget;
    Widget* w = widget;
    if(w != NULL && fn_w != NULL)
    {
        std::vector<std::string> vs;
        fn_w->connectByName(widget->name, beaconName, fn_name, vs);
    }
}

void Gui::registerBeacon(const std::string& widgetName, const std::string& beaconName, const std::string& fn_widget, const std::string& fn_name)
{
    registeredBeacons.push_back(RegisteredBeacon(widgetName, beaconName, fn_widget, fn_name));
    
    Widget* fn_w = widgets.get(fn_widget);
    Widget* w = widgets.get(widgetName);
    if(w != NULL && fn_w != NULL)
    {
        std::vector<std::string> vs;
        fn_w->connectByName(widgetName, beaconName, fn_name, vs);
    }
}

void Gui::registerBeacon(const std::string& widgetName, const std::string& beaconName, const std::string& fn_widget, const std::string& fn_name, const std::string& arg1)
{
    registeredBeacons.push_back(RegisteredBeacon(widgetName, beaconName, fn_widget, fn_name, arg1));
    
    Widget* fn_w = widgets.get(fn_widget);
    Widget* w = widgets.get(widgetName);
    if(w != NULL && fn_w != NULL)
    {
        std::vector<std::string> vs;
        vs.push_back(arg1);
        fn_w->connectByName(widgetName, beaconName, fn_name, vs);
    }
}

void Gui::registerBeacon(const std::string& widgetName, const std::string& beaconName, const std::string& fn_widget, const std::string& fn_name, const std::string& arg1, const std::string& arg2)
{
    registeredBeacons.push_back(RegisteredBeacon(widgetName, beaconName, fn_widget, fn_name, arg1, arg2));
    
    Widget* fn_w = widgets.get(fn_widget);
    Widget* w = widgets.get(widgetName);
    if(w != NULL && fn_w != NULL)
    {
        std::vector<std::string> vs;
        vs.push_back(arg1);
        vs.push_back(arg2);
        fn_w->connectByName(widgetName, beaconName, fn_name, vs);
    }
}

#define GRO_GUI_VERSION_MAJOR 0
#define GRO_GUI_VERSION_MINOR 2
#define GRO_GUI_VERSION_BUGFIX 0

#define V_STR(arg) #arg
#define V_STRING(arg) V_STR(arg)

#define GRO_GUI_VERSION_STRING V_STRING(GRO_GUI_VERSION_MAJOR) "." V_STRING(GRO_GUI_VERSION_MINOR) "." V_STRING(GRO_GUI_VERSION_BUGFIX)




bool Gui::save(const std::string& file)
{
    widgetToXML.clear();
    
    TiXmlDocument doc;
 	TiXmlDeclaration* decl = new TiXmlDeclaration("1.0", "", "");  
	doc.LinkEndChild(decl);
 
	TiXmlElement* root = new TiXmlElement("GRO_Gui_XML");  
	doc.LinkEndChild(root);
	
	root->SetAttribute("version", GRO_GUI_VERSION_STRING);
	
	TiXmlElement* gui = new TiXmlElement("Gui");
	root->LinkEndChild(gui);
	
	/*
	To save:
        Point pos;
        Dimensions dims;
        
        std::list<Panel*> panels;
        std::list<Cursor*> cursors;
        std::list<Shortcut*> shortcuts;
    */
	
	gui->SetAttribute("pos", pos.toString().c_str());
	gui->SetAttribute("dims", dims.toString().c_str());
	
	
    for (list<Cursor*>::iterator e = cursors.begin(); e != cursors.end(); e++)
    {
        //(*e)->save(gui);
    }
    for (list<Shortcut*>::iterator e = shortcuts.begin(); e != shortcuts.end(); e++)
    {
        //(*e)->save(gui);
    }

    for (list<Panel*>::iterator e = panels.begin(); e != panels.end(); e++)
    {
        (*e)->save(gui);
    }
	
	// Save beacons
	for(std::list<Gui::RegisteredBeacon>::iterator e = registeredBeacons.begin(); e != registeredBeacons.end(); e++)
    {
        std::string s;
        // Write out: widget.myfn(arg1, arg2)
        if((*e).fn_widget.size() > 0)
            s += (*e).fn_widget;
        else
        {
            char buf[40];
            snprintf(buf, 40, "[%d]", (*e).fn_widgetPtr->id.getValue());
            s += buf;
        }
        s += "." + (*e).fn + "(";
        bool firstArg = true;
        for(std::vector<std::string>::iterator f = (*e).args.begin(); f != (*e).args.end(); f++)
        {
            if(!firstArg)
                s += ", ";
            s += *f;
            firstArg = false;
        }
        s += ")";
        
        if(s.size() > 0)
        {
            std::map<Widget*, TiXmlElement*>::iterator g = widgetToXML.find((*e).widgetPtr);
            if(g != widgetToXML.end())
            {
                TiXmlElement* w = (*g).second;
                w->SetAttribute((*e).beacon.c_str(), s.c_str());
            }
            s.clear();
        }
    }
	
	
	bool result = doc.SaveFile(file.c_str());
	
	doc.Clear();
	
	return result;
}

void Gui::saveBeacon(TiXmlElement* w, Gui* gui, const std::string& name, const std::string& beaconName)
{
	std::string s;
	bool first = true;
	// Write out: widget1.myfn1(); widget2.myfn2(arg1); widget3.myfn3()
    for(std::list<Gui::RegisteredBeacon>::iterator e = gui->registeredBeacons.begin(); e != gui->registeredBeacons.end(); e++)
    {
        if((*e).widget == name && (*e).beacon == beaconName)
        {
            if(!first)
                s += "; ";
                
            // Write out: widget.myfn(arg1, arg2)
            s += (*e).fn_widget + "." + (*e).fn + "(";
            bool firstArg = true;
            for(std::vector<std::string>::iterator f = (*e).args.begin(); f != (*e).args.end(); f++)
            {
                if(!firstArg)
                    s += ", ";
                s += *f;
                firstArg = false;
            }
            s += ")";
            first = false;
        }
    }
    if(s.size() > 0)
    {
        w->SetAttribute(beaconName.c_str(), s.c_str());
        s.clear();
    }
}

void _saveBeacon(TiXmlElement* w, Gui* gui, const std::string& name, const std::string& beaconName)
{
	std::string s;
	bool first = true;
	// Write out: widget1.myfn1(); widget2.myfn2(arg1); widget3.myfn3()
    for(std::list<Gui::RegisteredBeacon>::iterator e = gui->registeredBeacons.begin(); e != gui->registeredBeacons.end(); e++)
    {
        if((*e).widget == name && (*e).beacon == beaconName)
        {
            if(!first)
                s += "; ";
                
            // Write out: widget.myfn(arg1, arg2)
            s += (*e).fn_widget + "." + (*e).fn + "(";
            bool firstArg = true;
            for(std::vector<std::string>::iterator f = (*e).args.begin(); f != (*e).args.end(); f++)
            {
                if(!firstArg)
                    s += ", ";
                s += *f;
                firstArg = false;
            }
            s += ")";
            first = false;
        }
    }
    if(s.size() > 0)
    {
        w->SetAttribute(beaconName.c_str(), s.c_str());
        s.clear();
    }
}

void saveBeacons(TiXmlElement* w, Gui* gui, const std::string& name)
{
	if(w == NULL || gui == NULL || name.size() == 0)
        return;
    
    gui->saveBeacon(w, gui, name, "onPress");
    gui->saveBeacon(w, gui, name, "onRelease");
    gui->saveBeacon(w, gui, name, "onClick");
    gui->saveBeacon(w, gui, name, "onDoubleClick");
    gui->saveBeacon(w, gui, name, "onScrollUp");
    gui->saveBeacon(w, gui, name, "onScrollDown");
    gui->saveBeacon(w, gui, name, "onMotion");
    gui->saveBeacon(w, gui, name, "onDrag");
    gui->saveBeacon(w, gui, name, "onHover");
    gui->saveBeacon(w, gui, name, "onEnter");
    gui->saveBeacon(w, gui, name, "onLeave");
}

void Widget::saveWidgetAttributes(TiXmlElement* w)
{
    if(name.size() != 0)
        w->SetAttribute("name", name.c_str());
    w->SetAttribute("id", id.getValue());
	
	if(pos != Point(0,0))
        w->SetAttribute("pos", pos.toString().c_str());
    if(dims != Dimensions(0,0))
        w->SetAttribute("dims", dims.toString().c_str());
	
	if(_depth != 128)
        w->SetAttribute("depth", _depth);
    if(_hidden)
        w->SetAttribute("hidden", _hidden);
    if(_folded)
        w->SetAttribute("folded", _folded);
	if(grabScrollFocus)
        w->SetAttribute("grabScrollFocus", grabScrollFocus);
    if(grabKeyboardFocus)
        w->SetAttribute("grabKeyboardFocus", grabKeyboardFocus);
	
	if(designPush != NULL)
        w->SetAttribute("designPush", designPush->toString().c_str());
	
	if(_gui != NULL)
        _gui->widgetToXML.insert(make_pair(this, w));
	
    //saveBeacons(w, _gui, this->name);
    
	
	/*
    PainterSet painters;
    */
}


}


